﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using ModbusLib.Base;

namespace ModbusLib
{
    public abstract class ModbusMaster
    {
        protected List<AsyncModel> AsyncModels = new List<AsyncModel>();

        public virtual void Connect() { }
        public virtual void Disconnect() { }

        /// <summary>
        /// 同步读取
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">读取数量（ushort）</param>
        /// <param name="callback">回调操作</param>
        public virtual byte[] Read(byte slave, FunctionType func, ushort start, ushort count,Action<dynamic> callback = null)
        {
            return null;
        }

        public virtual void ReadAsync(
            byte slave, FunctionType func, ushort start, ushort count,
            int handler, Action<byte[], int, Exception> callback)
        {
        }

        /// <summary>
        /// 同步写入
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">写入数量（ushort）</param>
        /// <param name="datas">写入数据</param>
        /// <param name="callback">回调操作</param>
        public virtual void Write(byte slave, FunctionType func, ushort start, ushort count, byte[] datas,Action<dynamic> callback = null) { }

        /// <summary>
        /// 异步写入
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">写入数量（ushort）</param>
        /// <param name="datas">写入数据</param>
        /// <param name="handler">回调模型Id</param>
        /// <param name="callback">回调函数</param>
        public virtual void WriteAsync(byte slave, FunctionType func, ushort start, ushort count, byte[] datas,
            int handler, Action<byte[], int, Exception> callback)
        {
        }

        // 00001
        // 10001
        // 30001
        // 40001

        /// <summary>
        /// 字符串地址读取
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="variable">存储区地址</param>
        /// <param name="isBaseOne">是否是从1开始，（默认 false）</param>
        /// <returns></returns>
        public byte[] Read(byte slave, string variable, bool isBaseOne = false,Action<object> callback = null)
        {
            AddrVal addrVal = GetAddress(variable, true, isBaseOne);

            return this.Read(slave, addrVal.Func, addrVal.StartAddr, 1,callback);
        }

        /// <summary>
        /// 字符串地址写入
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="variable">存储区地址</param>
        /// <param name="count">写入数量</param>
        /// <param name="datas">写入数据</param>
        /// <param name="isBaseOne">是否是从1开始，（默认 false）</param>
        /// <returns></returns>
        public void Write(byte slave, string variable, ushort count, byte[] datas, bool isBaseOne = false,Action<object> callback = null)
        {
            AddrVal addrVal = GetAddress(variable, false, isBaseOne);

            this.Write(slave, addrVal.Func, addrVal.StartAddr, count, datas,callback);
        }

        /// <summary>
        /// 通过地址和读写状态得到起始地址和功能码
        /// </summary>
        /// <param name="variable">存储区地址</param>
        /// <param name="isRead">读取还是写入</param>
        /// <param name="isBaseOne">是否是从1开始，（默认 false）</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static AddrVal GetAddress(string variable,bool isRead, bool isBaseOne)
        {
            FunctionType funcType;
            char area = variable[0];
            string addr = variable.Substring(1);
            ushort startAddr = ushort.Parse(addr);
            /**
             * 应用端输入的都是 从 1 开始 
             * 
             * 设备端 需判断是从 0 开始，还是从 1 开始
             * isBaseOne true   10001  => 10001
               isBaseOne false  10001  => 10000
            */
            if (!isBaseOne)
                startAddr -= 1;

            if (area.Equals('0'))
                funcType = isRead ? FunctionType.RCoilStatus : FunctionType.WCoilStatus;
            else if (area.Equals('1'))
                funcType = FunctionType.RInputCoil;
            else if (area.Equals('3'))
                funcType = FunctionType.RInputRegister;
            else if (area.Equals('4'))
                funcType = isRead ? FunctionType.RHoldingRegister : FunctionType.WHoldingRegister;
            else
                throw new Exception("地址格式不正确，无法识别");
            return new AddrVal() { Func = funcType, StartAddr = startAddr };
        }

        /// <summary>
        /// 生成读数据的 PDU 片段
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="funcCode">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">读取数量</param>
        /// <returns></returns>
        protected byte[] GetReadBytes(byte slave, byte funcCode, ushort start, ushort count)
        {
            return new byte[]
            {
                slave,
                funcCode,
                (byte)(start / 256),
                (byte)(start % 256),
                (byte)(count / 256),
                (byte)(count % 256),
            };
        }

        /// <summary>
        /// 生成写数据的 PDU 片段
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="funcCode">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">写入数量</param>
        /// <param name="datas">写入数据</param>
        /// <returns></returns>
        protected byte[] GetWriteBytes(byte slave, byte funcCode, ushort start, int count, byte[] datas)
        {
            List<byte> buff = new List<byte>
            {
                slave,
                funcCode,
                (byte)(start / 256),
                (byte)(start % 256),
                (byte)(count / 256),
                (byte)(count % 256),
                (byte)datas.Length
            };
            buff.AddRange(datas);
            return buff.ToArray();
        }

        protected virtual byte[] SendAndReceive(byte[] bytes, int len) { return null; }

        /// <summary>
        /// 字节转换成对应的数据类型
        /// </summary>
        /// <typeparam name="T">要转换的目标类型</typeparam>
        /// <param name="datas">原始字节数据</param>
        /// <param name="endianType">字节序排列方式</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static List<T> GetData<T>(List<byte> datas, EndianType endianType = EndianType.ABCD)
        {
            List<T> result = new List<T>();
            if (typeof(T) == typeof(bool))
            {
                foreach (byte item in datas)
                {
                    for (int i = 0; i < 8; i++)
                    {
                        dynamic b = (item & (1 << i)) != 0;
                        result.Add(b);
                    }
                }
            }
            else
            {
                int size = Marshal.SizeOf<T>();
                if (datas.Count % size != 0)
                    throw new Exception("需要转换的字节数量无效");
                Type bitConverter = typeof(BitConverter);
                System.Reflection.MethodInfo[] methods = bitConverter.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                System.Reflection.MethodInfo? method = methods.FirstOrDefault(m => m.ReturnType == typeof(T) && m.GetParameters().Count() == 2);
                if (method == null)
                    throw new Exception("数据转换出错！未找到匹配的数据转换方法");

                for (int i = 0; i < datas.Count; i += size)
                {
                    List<byte> bytes = datas.GetRange(i, size);
                    byte[] sb = SwitchEndianType(bytes, endianType);

                    if (BitConverter.IsLittleEndian)
                        sb = sb.Reverse().ToArray();
                    result.Add((T)method.Invoke(bitConverter, new object[] { sb,0 }));
                }
            }
            return result;
        }

        /// <summary>
        /// 目标数据转换为对应的字节
        /// </summary>
        /// <typeparam name="T">源数据类型</typeparam>
        /// <param name="datas">源数据</param>
        /// <param name="endianType">字节序类型</param>
        /// <returns></returns>
        public static byte[] GetBytes<T>(T[] datas, EndianType endianType = EndianType.ABCD)
        {
            List<byte> buff = new List<byte>();
            if (typeof(T) == typeof(bool))
            {
                byte start = 0x00;
                for (int i = 0; i < datas.Length; i++)
                {
                    bool flag = bool.Parse(datas[i].ToString()) == true;
                    byte tmp = (byte)(flag ? 0x01 : 0x00);
                    tmp = (byte)(tmp << (i % 8));
                    start |= tmp;
                    if (i % 8 == 0)
                    {
                        buff.Add(start);
                        start = 0x00;
                    }
                }
            }
            else
            {
                foreach (dynamic item in datas)
                {
                    byte[] bytes = BitConverter.GetBytes(item);
                    if (BitConverter.IsLittleEndian)
                        bytes = bytes.Reverse().ToArray();
                    bytes = SwitchEndianType(bytes.ToList(), endianType);

                    buff.AddRange(bytes);
                }
            }

            return buff.ToArray();
        }

        /// <summary>
        /// 字节序转换
        /// </summary>
        /// <param name="datas"></param>
        /// <param name="endianType"></param>
        /// <returns></returns>
        private static byte[] SwitchEndianType(List<byte> datas, EndianType endianType)
        {
            switch (endianType)
            {
                case EndianType.ABCD:
                case EndianType.ABCDEFGH:
                    return datas.ToArray();
                case EndianType.DCBA:
                case EndianType.GHFEDCBA:
                    datas.Reverse();
                    return datas.ToArray();
                case EndianType.CDAB:
                    return new byte[] { datas[2], datas[3], datas[0], datas[1] };
                case EndianType.BADC:
                    return new byte[] { datas[1], datas[0], datas[3], datas[2] };

                case EndianType.GHEFCDAB:
                    return new byte[] { datas[6], datas[7], datas[4], datas[5], datas[2], datas[3], datas[0], datas[1] };
                case EndianType.BADCFEHG:
                    return new byte[] { datas[1], datas[0], datas[3], datas[2], datas[5], datas[4], datas[7], datas[6] };
            }
            return datas.ToArray();
        }
    }

    public class AddrVal
    {
        public FunctionType Func { get; set; }
        public ushort StartAddr { get; set; }
    }
}
